package jcu.sal.agent.netty;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

import jcu.sal.common.StreamID;
import jcu.sal.common.CommandFactory.Command;
import jcu.sal.common.agents.netty.NettyConstants;
import jcu.sal.common.agents.netty.NettyMessage;

import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.handler.codec.frame.FrameDecoder;

/**
 * @author Marc Hammerton
 *
 */
public class NettyAgentMessageDecoder extends FrameDecoder {

	private short type = 0;
	private int headerLength = 0;
	private int bodyLength = 0;
	
	/* (non-Javadoc)
	 * @see org.jboss.netty.handler.codec.frame.FrameDecoder#decode(org.jboss.netty.channel.ChannelHandlerContext, org.jboss.netty.channel.Channel, org.jboss.netty.buffer.ChannelBuffer)
	 */
	@Override
	protected Object decode(ChannelHandlerContext chx, Channel channel,
			ChannelBuffer buffer) throws Exception {
		
		// Wait until at least the short header is available
        if (buffer.readableBytes() < NettyConstants.HEADER_LENGTH_FIXED_DATALENGTH) {
            return null;
        }
        this.type = buffer.getShort(0);
		
		switch(this.type) {
			case NettyConstants.REQ_GET_ID:
			case NettyConstants.REQ_GET_TYPE:
			case NettyConstants.REQ_LIST_ACTIVE_SENSORS:
			case NettyConstants.REQ_LIST_PROTOCOLS:
			case NettyConstants.REQ_LIST_SENSORS: 
			case NettyConstants.REQ_UNREGISTER:	
				this.bodyLength = 0;
	        	this.headerLength = NettyConstants.HEADER_LENGTH_FIXED_DATALENGTH;
	        	break;
			case NettyConstants.REQ_REGISTER:
			case NettyConstants.REQ_ADD_PROTOCOL:
			case NettyConstants.REQ_ADD_SENSOR:
			case NettyConstants.REQ_GET_CML:
			case NettyConstants.REQ_LIST_SENSOR:
			case NettyConstants.REQ_REMOVE_PROTOCOL:
			case NettyConstants.REQ_REMOVE_SENSOR:
			case NettyConstants.REQ_SETUP_STREAM:
			case NettyConstants.REQ_START_STREAM:
			case NettyConstants.REQ_TERMINATE_STREAM:
			case NettyConstants.REQ_REG_EVENTHANDLER:
			case NettyConstants.REQ_UNREG_EVENTHANDLER:
				this.bodyLength = buffer.getInt(NettyConstants.HEADER_LENGTH_FIXED_DATALENGTH);
				this.headerLength = NettyConstants.HEADER_LENGTH_VAR_DATALENGTH;
				break;
	        default:
	        	this.bodyLength = 0;
	        	this.headerLength = NettyConstants.HEADER_LENGTH_FIXED_DATALENGTH;
	        	break;	
		}
		
		// Wait until the whole data is available.
        if (buffer.readableBytes() < (this.headerLength + this.bodyLength)) {
            return null;
        }

        // Skip the length field because we know it already.
        buffer.skipBytes(this.headerLength);
        NettyMessage message = new NettyMessage(type);
        switch (type) {
			case NettyConstants.REQ_ADD_PROTOCOL:
			case NettyConstants.REQ_REMOVE_PROTOCOL:
				message.addData(decodeBool(buffer));
				this.bodyLength -= 1;
			case NettyConstants.REQ_REGISTER:
			case NettyConstants.REQ_UNREGISTER:
			case NettyConstants.REQ_ADD_SENSOR:
			case NettyConstants.REQ_GET_CML:
			case NettyConstants.REQ_LIST_SENSOR:
			case NettyConstants.REQ_REMOVE_SENSOR:
			case NettyConstants.REQ_REG_EVENTHANDLER:
			case NettyConstants.REQ_UNREG_EVENTHANDLER:
				message.addData(decodeString(this.bodyLength, buffer));
				break;
			case NettyConstants.REQ_SETUP_STREAM:
				int bodyLength2 = buffer.readInt();
				message.addData(decodeCommand(bodyLength2, buffer));
				message.addData(decodeString((this.bodyLength - bodyLength2), buffer));
				break;
			case NettyConstants.REQ_START_STREAM:
			case NettyConstants.REQ_TERMINATE_STREAM:	
				message.addData(decodeStreamID(this.bodyLength, buffer));
				break;
			default:
				break;
		}
        
        return message;
	}
	

	private boolean decodeBool(ChannelBuffer buffer) {
		boolean value = buffer.readByte() == 1 ? true : false;
        return value;
	}
	
	private String decodeString(int length, ChannelBuffer buffer) {
		byte[] bytes = new byte[length];
		buffer.readBytes(bytes);
		String value = new String(bytes);
		
		return value;
	}
	
	private Command decodeCommand(int length, ChannelBuffer buffer) {
		byte[] bytes = new byte[length];
		buffer.readBytes(bytes);
		
		// Convert ByteArray to Object
		Command c = null;
		try {
			ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
			ObjectInputStream ois = new ObjectInputStream(bis);
			c = (Command) ois.readObject();
			ois.close();
			bis.close();
		} catch (IOException e) {
			System.out.println("IOException in Netty Message Decoder: ");
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			System.out.println("ClassNotFoundException in Netty Message Decoder: ");
			e.printStackTrace();
		}
		
		return c;
	}
	
	private StreamID decodeStreamID(int length, ChannelBuffer buffer) {
		byte[] bytes = new byte[length];
		buffer.readBytes(bytes);
		
		// Convert ByteArray to Object
		StreamID streamId = null;
		try {
			ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
			ObjectInputStream ois = new ObjectInputStream(bis);
			streamId = (StreamID) ois.readObject();
			ois.close();
			bis.close();
		} catch (IOException e) {
			System.out.println("IOException in Netty Message Decoder: ");
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			System.out.println("ClassNotFoundException in Netty Message Decoder: ");
			e.printStackTrace();
		}
		
		return streamId;		
	}
}
